概述
本节解决 Prisma 在多数据库类型场景下的核心问题:如何让同一个 PrismaModule 同时支持 MySQL 和 PostgreSQL 等不同类型的数据库。核心方案是利用 Prisma 的 generator output 配置,为不同数据库类型生成独立的 Client 文件,再通过 URL 协议自动选择对应的 Client。
Prisma Generate 与 Output 配置
默认行为
npx prisma generate 默认将生成的 Client 放在 node_modules/.prisma/client 目录下。所有代码中通过 import { PrismaClient } from '@prisma/client' 引用的都是这个默认位置。
自定义 Output 目录
在 schema.prisma 中指定 generator 的 output 属性:
// prisma/schema.prisma (MySQL 版本)
generator client {
provider = "prisma-client-js"
output = "../prisma/client-mysql"
}
datasource db {
provider = "mysql"
url = "mysql://root:password@localhost:3306/test_db"
}
prisma
// prisma/schema.prisma (PostgreSQL 版本)
generator client {
provider = "prisma-client-js"
output = "../prisma/client-postgresql"
}
datasource db {
provider = "postgresql"
url = "postgresql://pg_user:example@localhost:5432/test_db"
}
prisma
生成 Client 文件
分别修改 schema.prisma 后执行生成命令:
# 生成 MySQL Client
# 1. 设置 provider = "mysql", output = "../prisma/client-mysql"
npx prisma generate
# 生成 PostgreSQL Client
# 2. 修改 provider = "postgresql", output = "../prisma/client-postgresql"
npx prisma generate
bash
生成后的目录结构:
prisma/
├── client-mysql/ # MySQL 专用 Client
│ ├── index.js
│ ├── index.d.ts
│ └── package.json
└── client-postgresql/ # PostgreSQL 专用 Client
├── index.js
├── index.d.ts
└── package.json
text
生成的 Client 目录中包含
package.json,已配置好不同模块系统(CommonJS / ESM)的入口文件,无需手动处理。
将 Client 注册为本地包
使用 pnpm link 将生成的 Client 目录注册为项目依赖:
# 注册 MySQL Client
pnpm add prisma-mysql@link:./prisma/client-mysql
# 注册 PostgreSQL Client
pnpm add prisma-postgresql@link:./prisma/client-postgresql
bash
NPM 和 Yarn 的等效命令:
# NPM
npm add prisma-mysql@file:./prisma/client-mysql
# Yarn
yarn add prisma-mysql@link:./prisma/client-mysql
bash
注册后在 package.json 中会看到软链接:
{
"dependencies": {
"prisma-mysql": "link:./prisma/client-mysql",
"prisma-postgresql": "link:./prisma/client-postgresql"
}
}
json
URL 解析与 Client 自动选择
工具函数
创建 prisma.utils.ts,根据 URL 协议自动判断数据库类型:
// prisma/prisma.utils.ts
const protocolRegex = /^([a-z]+):\/\//;
export function getDBType(url: string): string {
const matches = url.match(protocolRegex);
const protocol = matches?.[1] || 'file';
if (protocol === 'file') {
return 'sqlite';
}
return protocol; // 'mysql', 'postgresql', 'mongodb' 等
}
typescript
Core Module 中根据类型选择 Client
// prisma/prisma-core.module.ts
import { PrismaClient as MySQLClient } from 'prisma-mysql';
import { PrismaClient as PGClient } from 'prisma-postgresql';
import { getDBType } from './prisma.utils';
export class PrismaCoreModule {
static forRoot(options: PrismaModuleOptions) {
const url = options.url;
const dbType = getDBType(url);
const prismaClientProvider: Provider = {
provide: 'PRISMA_CLIENT',
useFactory: () => {
const clientOptions = {
datasources: { db: { url } },
...options.options,
};
if (dbType === 'mysql') {
return new MySQLClient(clientOptions);
} else if (dbType === 'postgresql') {
return new PGClient(clientOptions);
}
throw new Error(`Unsupported database type: ${dbType}`);
},
};
return {
module: PrismaCoreModule,
providers: [prismaClientProvider],
exports: [prismaClientProvider],
};
}
}
typescript
Controller 中使用
// app.controller.ts
import { PrismaClient } from '@prisma/client';
@Controller()
export class AppController {
constructor(@Inject('PRISMA_CLIENT') private readonly prisma: PrismaClient) {}
@Get()
async hello() {
return this.prisma.user.findMany();
}
}
typescript
注入类型仍然使用
@prisma/client的PrismaClient,因为只有官方包中的类型才包含完整的 model 方法(如.user.findMany())。实际的运行时实例由 Core Module 根据数据库类型动态创建。
多连接区分问题
当前实现中,如果 AppModule 注册多个 PrismaModule.forRoot(),无法区分不同连接:
// 存在问题:两个 forRoot 无法区分
PrismaModule.forRoot('mysql://...'),
PrismaModule.forRoot('postgresql://...'),
typescript
后续需要添加 connectionName 或类似的标识来区分不同的数据库连接。
从官方模块获得的启发
TypeORM 的工厂方法
@nestjs/typeorm 的 forRootAsync 提供了 dataSourceFactory,允许用户自定义连接创建过程,类似前端的生命周期钩子。
Mongoose 的错误处理工厂
@nestjs/mongoose 提供了两个工厂函数:
connectionFactory-- 自定义连接创建过程connectionErrorFactory-- 自定义连接错误处理
这两个工厂函数就像面向切面编程(AOP)中的切面,在不修改核心逻辑的情况下扩展行为。
Prisma 的局限性
Prisma Client 本身提供了部分配置(如 errorFormat、rejectOnNotFound),但没有提供完整的生命周期钩子。需要通过自定义 Module 补充以下能力:
- 连接区分 -- 通过 connectionName 区分多个连接
- 连接生命周期 -- 创建、销毁、错误处理
- 断线重试 -- 使用 RxJS 实现自动重连
这些能力将通过 RxJS 的 lastValueFrom、defer、catchError 等操作符实现,正是 @nestjs/mongoose 等官方模块所采用的方式。
关键要点
- generator output -- Prisma 通过
output配置将不同数据库的 Client 生成到指定目录 - link 协议 -- 使用
pnpm add <name>@link:<path>将本地目录注册为包依赖 - URL 协议解析 -- 通过正则匹配 URL 协议(mysql://、postgresql://)自动选择对应 Client
- 类型复用 -- 注入时使用
@prisma/client的类型定义,运行时使用对应数据库的 Client 实例 - 生命周期补充 -- Prisma 缺少完整的生命周期钩子,需要参考 Mongoose 官方模块用 RxJS 补充
↑